package tum0r.webengine.models.utils

import tum0r.webengine.extension.closeableClose
import tum0r.webengine.extension.isNotNullOrBlank
import tum0r.webengine.models.device.DeviceInfo
import java.io.BufferedWriter
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.util.*
import java.util.concurrent.TimeUnit

/**
 * 说明: 执行Shell命令
 *
 * 创建者: tum0r
 *
 * 时间: 2021/8/28 12:44
 *
 * @param[shellPath] Shell程序路径
 * @param[options] Shell的参数
 * @param[timeout] 超时时间，0为不超时
 * @param[timeUnit] 超时时间的单位，默认为秒
 */
data class Shell(val shellPath: String, val options: String = "-c", var timeout: Long = 0L, var timeUnit: TimeUnit = TimeUnit.SECONDS) {
    /**
     * 命令执行后的返回状态，0为正常执行
     */
    var state: Int = 0

    /**
     * 命令执行结果
     */
    var result: String = ""

    /**
     * 命令是否正常执行成功
     */
    fun isSuccess(): Boolean = state == 0

    /**
     * 执行系统命令伴生对象
     */
    companion object {
        private val deviceInfo = DeviceInfo.getInstance()

        /**
         * 获取默认的Shell执行对象
         *
         * @return Windows调用C:\Windows\system32\cmd.exe，Linux和macOS读取/etc/DeviceInfo.SHELLS文件获取所有的shell，按照优先顺序 zsh -> bash -> dash -> tcsh -> csh -> ksh，如果都不存在则为 sh 或 空字符串
         */
        @JvmStatic
        fun defaultShell(): Shell {
            val osName = deviceInfo.os.name.lowercase(Locale.getDefault())
            val shellPath: String
            val option: String
            if (osName.contains("mac") || osName.contains("linux")) {
                shellPath = if (deviceInfo.shells.containsKey("zsh")) deviceInfo.shells["zsh"]!! else if (deviceInfo.shells.containsKey("bash")) deviceInfo.shells["bash"]!! else if (deviceInfo.shells.containsKey("dash")) deviceInfo.shells["dash"]!!
                else if (deviceInfo.shells.containsKey("tcsh")) deviceInfo.shells["tcsh"]!! else if (deviceInfo.shells.containsKey("csh")) deviceInfo.shells["csh"]!! else if (deviceInfo.shells.containsKey("ksh")) deviceInfo.shells["ksh"]!!
                else deviceInfo.shells["sh"] ?: ""
                option = "-c"
            } else {
                shellPath = deviceInfo.shells["cmd"]!!
                option = "/c"
            }
            return Shell(shellPath, option)
        }
    }

    /**
     * 执行系统命令
     *
     * @param[command] 被执行的命令
     * @param[args] 命令需要在执行时另外的输入
     */
    @JvmOverloads
    fun execute(command: String, args: String? = null) {
        var state = 1
        val result = StringBuilder()

        try {
            val processBuilder = ProcessBuilder(shellPath, options, command)
            processBuilder.redirectErrorStream(true)
            val process = processBuilder.start()
            if (args.isNotNullOrBlank()) {
                val outputStream = process.outputStream
                val outputStreamWriter = OutputStreamWriter(outputStream)
                val bufferedWriter = BufferedWriter(outputStreamWriter)
                bufferedWriter.write(args!!)
                bufferedWriter.flush()
                closeableClose(bufferedWriter, outputStreamWriter, outputStream)
            }
            if (timeout == 0L) state = process.waitFor()
            else {
                if (process.waitFor(timeout, timeUnit)) state = process.exitValue()
                else {
                    state = 62
                    result.append("执行命令超时")
                }
            }
            val inputStream = process.inputStream
            val inputStreamReader = InputStreamReader(inputStream)
            val text = inputStreamReader.readText()
            if (text.isNotBlank()) result.append(text)
            closeableClose(inputStreamReader, inputStream)
        } catch (e: Exception) {
            result.append(e.message)
        }

        this.state = state
        this.result = result.toString().trim()
    }
}